﻿using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Controls.Primitives;
using System.Collections.Generic;
using System.Windows.Media.Effects;
using System.Linq;
using System.Text.RegularExpressions;

namespace FileUploader
{
    public enum TriggerTypes { LeftClick, RightClick, Hover }

    public class PopupMenu
    {
        public EventHandler<MouseEventArgs> Opening { get; set; }

        public Grid RootGrid { get; set; }
        public Popup MenuPopup { get; set; }
        public ListBox ListBox { get; set; }
        public Effect ListBoxEffect { get; set; }
        public Effect ItemsEffect { get; set; }

        IEnumerable<UIElement> ClickedElements { get; set; }

        //private bool IsHoveringTriggerElement { get; set; }
        private bool IsHoveringMenu { get; set; }
        public double OffsetX { get; set; }
        public double OffsetY { get; set; }

        public ListBoxItem GetListBoxItem(int index)
        {
            return (ListBoxItem)(ListBox.ItemContainerGenerator.ContainerFromItem(ListBox.Items[index]));
        }

        public ItemCollection Items
        {
            get { return ListBox.Items; }
            set
            {
                ListBox.Items.Clear();
                while (value.GetEnumerator().MoveNext())
                    ListBox.Items.Add(value.GetEnumerator().Current);
            }
        }

        public FrameworkElement Item(int index)
        {
            return (FrameworkElement)ListBox.Items[index];
        }

        public T Item<T>(int index)
        {
            return (T)ListBox.Items[index];
        }

        public PopupMenu()
            : this(0, 0)
        { }

        public PopupMenu(double offsetX, double offsetY)
            : this(new ListBox { Background = new SolidColorBrush(Color.FromArgb(255, 240, 240, 240)) },
                 new DropShadowEffect { ShadowDepth = 0, Color = Colors.White, Opacity = 5, BlurRadius = 15 }, offsetX, offsetY)
        { }

        public PopupMenu(ListBox listBox)
            : this(listBox, new DropShadowEffect { ShadowDepth = 0, Color = Colors.White, Opacity = 1, BlurRadius = 15 }, 0, 0)
        { }

        public PopupMenu(ListBox listBox, double offsetX, double offsetY)
            : this(listBox, new DropShadowEffect { ShadowDepth = 0, Color = Colors.White, Opacity = 1, BlurRadius = 15 }, offsetX, offsetY)
        { }

        public PopupMenu(ListBox listBox, Effect itemsEffect, double offsetX, double offsetY)
        {
            OffsetX = offsetX;
            OffsetY = offsetY;

            MenuPopup = new Popup();

            ListBox = listBox;
            if (ListBox.Effect == null)
                ListBox.Effect = new DropShadowEffect { Color = Colors.Black, BlurRadius = 4, Opacity = 0.5, ShadowDepth = 2 };

            ItemsEffect = itemsEffect;

            MenuPopup.Child = GeneratePopupContent();

            //RootGrid.Loaded += (sender, e) =>
            //{
            //    FrameworkElement t = sender as FrameworkElement;
            //    var p = (FrameworkElement)VisualTreeHelper.GetParent(t);
            //    if (p != null) p.HorizontalAlignment = HorizontalAlignment.Stretch;
            //};
        }

        private Grid GeneratePopupContent()
        {
            Grid outerGrid = new Grid
            {
                Width = Application.Current.Host.Content.ActualWidth,
                Height = Application.Current.Host.Content.ActualHeight,
                Background = new SolidColorBrush(Colors.Transparent)
            };

            Canvas outerCanvas = new Canvas
            {
                Width = outerGrid.Width,
                Height = outerGrid.Height
            };

            RootGrid = new Grid
            {
                HorizontalAlignment = HorizontalAlignment.Left,
                VerticalAlignment = VerticalAlignment.Top,
            };

            outerGrid.Children.Add(outerCanvas);
            outerCanvas.Children.Add(RootGrid);

            if (ListBox.Parent != null)
            {
                if (ListBox.Parent is Panel)
                    (ListBox.Parent as Panel).Children.Remove(ListBox);
                else
                    throw new Exception("The template listbox must be placed in a container that inherits from type Panel");
            }
            RootGrid.Children.Add(ListBox);

            // Close the menu when a click is made outside popup itself
            outerGrid.MouseLeftButtonDown += delegate
            {
                this.Close();
            };

            outerGrid.MouseRightButtonDown += (sender, e) =>
            {
                e.Handled = true;
                this.Close();
            };
            return outerGrid;
        }

        private static FrameworkElement GetRootLayoutElement(FrameworkElement element)
        {
            while ((element != null && element.Parent != null && element.Parent is FrameworkElement))
                element = (FrameworkElement)element.Parent;
            return element;
        }

        public void AddItem(string text, MouseButtonEventHandler leftClickHandler)
        {
            AddItem(text, null, leftClickHandler);
        }

        public void AddItem(string text, string tag, MouseButtonEventHandler leftClickHandler)
        {
            AddItem(new TextBlock() { Text = text, Tag = tag }, leftClickHandler);
        }

        public void AddItem(FrameworkElement item)
        {
            AddItem(item, null);
        }

        public void AddItem(FrameworkElement item, MouseButtonEventHandler leftClickHandler)
        {
            foreach (FrameworkElement child in item.GetVisualChildrenAndSelf())
                if (child.Effect == null)
                    child.Effect = ItemsEffect;

            item.HorizontalAlignment = HorizontalAlignment.Stretch;
            ListBox.HorizontalAlignment = HorizontalAlignment.Stretch;

            if (leftClickHandler != null)
                item.MouseLeftButtonUp += delegate { this.Close(); } + leftClickHandler;

            if (item.Parent != null)
                (item.Parent as Panel).Children.Remove(item);

            ListBox.Items.Add(item);
            ListBoxItem container = GetContainer<ListBoxItem>(item);
            if (container != null)
                container.Name = item.Name + "Container";

        }

        public void AddTrigger(TriggerTypes triggerType, params UIElement[] triggerElements)
        {
            foreach (FrameworkElement triggerElement in triggerElements)
            {
                switch (triggerType)
                {

                    case TriggerTypes.RightClick:
                        triggerElement.MouseRightButtonDown += (sender, e) =>
                        {
                            e.Handled = true;
                        };

                        triggerElement.MouseRightButtonUp += (sender, e) =>
                        {
                            Point mousePos = triggerElement.TransformToVisual(null).Transform(e.GetPosition(triggerElement));
                            this.Open(mousePos, sender, e);
                        };
                        break;

                    case TriggerTypes.Hover:
                        triggerElement.MouseEnter += (sender, e) =>
                        {
                            IsHoveringMenu = true;
                            Point mousePos = triggerElement.TransformToVisual(null).Transform(new Point());
                            mousePos.Y += triggerElement.ActualHeight;
                            this.Open(mousePos, sender, e);
                        };

                        RootGrid.MouseLeave += (sender, e) =>
                        {
                            if (IsHoveringMenu)// && sender == RootGrid)
                            {
                                IsHoveringMenu = false;
                                this.Close();
                            }
                        };
                        break;

                    case TriggerTypes.LeftClick:
                        triggerElement.MouseLeftButtonDown += (sender, e) =>
                        {
                            if (MenuPopup.IsOpen)
                            {
                                this.Close();
                            }
                            else
                            {
                                Point mousePos = triggerElement.TransformToVisual(null).Transform(new Point());
                                mousePos.Y += triggerElement.ActualHeight;
                                this.Open(mousePos, sender, e);
                            }
                        };
                        break;
                }
            }
        }

        public void Open(Point mousePos, object sender, MouseEventArgs e)
        {
            UIElement LayoutRoot = GetRootLayoutElement((FrameworkElement)sender);
            ClickedElements = VisualTreeHelper.FindElementsInHostCoordinates(mousePos, LayoutRoot);
            RootGrid.Margin = new Thickness(mousePos.X + OffsetX, mousePos.Y + OffsetY, 0, 0);
            ListBox.SelectedIndex = -1; // Reset selected item

            // Invoking the event via a dispatcher to make sure the visual tree for our listbox is created before the event handler is called
            if (Opening != null)
                ListBox.Dispatcher.BeginInvoke(() => Opening.Invoke(sender, e));

            MenuPopup.IsOpen = true;

            Animate(RootGrid, "UIElement.Opacity", 0, RootGrid.Opacity, TimeSpan.FromMilliseconds(200));
            //Animate(ListBox, "(UIElement.Projection).(PlaneProjection.RotationY)", 90, 0, TimeSpan.FromMilliseconds(200));
        }

        public void Close()
        {
            MenuPopup.IsOpen = false;
        }


        public static Storyboard Animate(FrameworkElement element, string targetProperty, double? from, double? to, Duration duration)
        {
            DoubleAnimation da = new DoubleAnimation { From = from, To = to, Duration = duration };
            Storyboard.SetTarget(da, element);
            Storyboard.SetTargetProperty(da, new PropertyPath(targetProperty));
            Storyboard sb = new Storyboard();
            sb.Children.Add(da);
            sb.Begin();
            return sb;
        }

        public T FindClickedElement<T>()
        {
            return FindElement<T>(ClickedElements);
        }

        private static T FindElement<T>(IEnumerable<UIElement> elements)
        {
            return FindElement<T>(elements, 0);
        }

        private static T FindElement<T>(IEnumerable<UIElement> elements, int index)
        {
            foreach (object ele in elements)
                if (ele is T && index-- <= 0)
                    return (T)ele;
            return default(T);
        }

        public List<ListBoxItem> FindItemContainersByTag(object tag)
        {
            return FindItemsByTag(tag)
                .Select(e => GetContainer<ListBoxItem>(e)).ToList();
        }

        public List<FrameworkElement> FindItemsByTag(object tag)
        {
            return Items.ToArray()
                .Where(i => i is FrameworkElement && ((i as FrameworkElement).Tag ?? "") == tag)
                .Select(i => i as FrameworkElement).ToList();
        }

        public List<ListBoxItem> FindItemContainersByName(string regexPattern)
        {
            return FindItemsByName(regexPattern).Select(e => GetContainer<ListBoxItem>(e)).ToList();
        }

        public List<FrameworkElement> FindItemsByName(string regexPattern)
        {
            return Items.ToArray()
                .Where(i => i is FrameworkElement && (new Regex(regexPattern).IsMatch((i as FrameworkElement).Name ?? "")))
                .Select(i => i as FrameworkElement).ToList();
        }

        public T GetContainer<T>(object item)
        {
            return (T)(ListBox.ItemContainerGenerator.ContainerFromItem(item) as object);
        }

        public T GetItem<T>(string name)
        {
            return Items.ToArray()
                .Where(i => i is T && (i as FrameworkElement).Name == name)
                .Select(i => (T)i).First();
        }
    }
}
